Introduction

This document will serve as a tutorial for using SCISSORS, with the added functionality of detailing exactly how the PBMC3k dataset figures presented in our manuscript were generated. We’ll start from a 10X counts matrix and end with fully annotated cell clusters. In addition to R, in order to run all the code in this document you’ll need a Python 3 installation with openTSNE and all its dependencies installed.

Libraries

library(pals)        # basic colors
library(dplyr)       # tidy data manipulation
library(Seurat)      # single cell infrastructure
library(ggplot2)     # plots
library(SCISSORS)    # our package 
library(paletteer)   # advanced colors
library(reticulate)  # Python interface
library(SeuratData)  # PBMC3k dataset

Data

We load a scRNA-seq dataset provided by 10X Genomics that consists of 2,700 peripheral blood mononuclear cells (PBMCs) from a healthy donor.

pbmc <- LoadData("pbmc3k")

Preprocessing

Here we use PrepareData() to normalize expression & select highly variable genes through sctransform, run PCA & t-SNE. and cluster our cells. We utilize 15 principal components even though the Satija Lab vignette used 10, as we use sctransform normalization, which does a better job of retaining biological heterogeneity through normalization than standard log-normalization.

pbmc <- PrepareData(pbmc, 
                    n.variable.genes = 4000, 
                    n.PC = 15, 
                    which.dim.reduc = "tsne",
                    initial.resolution = .4, 
                    random.seed = 629)
## [1] "Normalizing counts using SCTransform"
## [1] "Running t-SNE on 15 principal components with perplexity = 30"
## [1] "Clustering cells in PCA space using k ~ 52 & resolution = 0.4"
## [1] "Found 6 unique clusters"
p0 <- DimPlot(pbmc, cols = cols25()) + 
      labs(x = "t-SNE 1", y = "t-SNE 2") + 
      theme_yehlab() + 
      theme(legend.position = "bottom", 
            legend.justification = "center", 
            legend.direction = "horizontal") + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p0

Fit-SNE

We’ll also run the Fast Fourier Transform-accelerated version of t-SNE as implemented in the Python library openTSNE. First we’ll need to get our PC matrix into a form accessible by our Python interpreter.

pc_mat <- Embeddings(pbmc, reduction = "pca")
# import libraries
import numpy as np
import pandas as pd
from openTSNE import TSNEEmbedding
from openTSNE import initialization
from openTSNE.affinity import Multiscale
# import PC matrix
pc_mat = np.array(r.pc_mat)
# run Fit-SNE w/ multiscale kernel
affin_multi = Multiscale(pc_mat, perplexities=[30, 150], metric='cosine', random_state=629)
init = initialization.pca(pc_mat, random_state=629)
tsne_obj = TSNEEmbedding(init, affin_multi, negative_gradient_method='fft')
embed = tsne_obj.optimize(n_iter=300, exaggeration=12, momentum=0.6)
embed_multi = embed.optimize(n_iter=600, exaggeration=1, momentum=0.8)

Now we pull the results back into R, and save them in our Seurat object. We save the original embedding (made using the default Barnes-Hut t-SNE implementation) in pbmc@reduction$bh_tsne - we need to do this in order to make the Fit-SNE embedding the default embedding that will be retrieved in calls to functions such as DimPlot() or FeaturePlot().

embed <- as.matrix(py$embed_multi)
rownames(embed) <- colnames(pbmc)
colnames(embed) <- c("Fit-SNE_1", "Fit-SNE_2")
pbmc@reductions$bh_tsne <- pbmc@reductions$tsne
pbmc@reductions$tsne <- CreateDimReducObject(embeddings = embed, 
                                             key = "FitSNE_", 
                                             assay = "SCT", 
                                             global = TRUE)

Visualizing the results shows pretty much the same global structure as with the default t-SNE implementation, albeit rotated a bit, but I like that Fit-SNE’s clusters are a bit denser, so we’ll use Fit-SNE going forward.

p1 <- DimPlot(pbmc, cols = cols25()) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      theme(legend.position = "bottom", 
            legend.justification = "center", 
            legend.direction = "horizontal") + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p1

Reclustering

Here we’ll examine clusters 0, 1, and 2. Cluster 0 seems large enough, and has enough variability on the X-axis of the t-SNE embedding to appear as though it might harbor subgroups. Clusters 1 and 2 have small but visible subclusters.

reclust_res <- ReclusterCells(pbmc, 
                              which.clust = list(0, 1, 2), 
                              n.variable.genes = 4000,
                              n.PC = 15, 
                              k.vals = c(5, 10, 15, 20, 25), 
                              resolution.vals = c(.1, .2, .3, .4), 
                              which.dim.reduc = "tsne", 
                              redo.embedding = TRUE, 
                              random.seed = 629)
## [1] "Reclustering cells in cluster 0 using k = 5 & resolution = 0.2, which achieved silhouette score: 0.394"
## [1] "Reclustering cells in cluster 1 using k = 33 & resolution = 0.2, which achieved silhouette score: 0.371"
## [1] "Reclustering cells in cluster 2 using k = 5 & resolution = 0.1, which achieved silhouette score: 0.6"

Now we’ll run Fit-SNE on each of the reclustered objects for consistencies sake. First we’ll need to isolate the PC matrices and send them to Python.

pc_clust0 <- Embeddings(reclust_res[[1]], reduction = "pca")
pc_clust1 <- Embeddings(reclust_res[[2]], reduction = "pca")
pc_clust2 <- Embeddings(reclust_res[[3]], reduction = "pca")

Running Fit-SNE three times could probably be cleaned up and done in a for loop, but this was easiest to write out in a short time. We use different perplexity sets for each cluster, as they’re all composed of differing numbers of cells.

# import PC matrices
pc_clust0 = np.array(r.pc_clust0)
pc_clust1 = np.array(r.pc_clust1)
pc_clust2 = np.array(r.pc_clust2)

# run Fit-SNE w/ multiscale kernel - cluster 0
affin_multi_clust0 = Multiscale(pc_clust0, perplexities=[15, 50], metric='cosine', random_state=629)
init_clust0 = initialization.pca(pc_clust0, random_state=629)
tsne_obj_clust0 = TSNEEmbedding(init_clust0, affin_multi_clust0, negative_gradient_method='fft')
embed_clust0 = tsne_obj_clust0.optimize(n_iter=300, exaggeration=12, momentum=0.7)
embed_multi_clust0 = embed_clust0.optimize(n_iter=850, exaggeration=1, momentum=0.8)

# run Fit-SNE w/ multiscale kernel - cluster 1
affin_multi_clust1 = Multiscale(pc_clust1, perplexities=[15, 30], metric='cosine', random_state=629)
init_clust1 = initialization.pca(pc_clust1, random_state=629)
tsne_obj_clust1 = TSNEEmbedding(init_clust1, affin_multi_clust1, negative_gradient_method='fft')
embed_clust1 = tsne_obj_clust1.optimize(n_iter=300, exaggeration=12, momentum=0.6)
embed_multi_clust1 = embed_clust1.optimize(n_iter=850, exaggeration=1, momentum=0.8)

# run Fit-SNE w/ multiscale kernel - cluster 2
affin_multi_clust2 = Multiscale(pc_clust2, perplexities=[10, 30], metric='cosine', random_state=629)
init_clust2 = initialization.pca(pc_clust2, random_state=629)
tsne_obj_clust2 = TSNEEmbedding(init_clust2, affin_multi_clust2, negative_gradient_method='fft')
embed_clust2 = tsne_obj_clust2.optimize(n_iter=300, exaggeration=12, momentum=0.6)
embed_multi_clust2 = embed_clust2.optimize(n_iter=750, exaggeration=1, momentum=0.8)

Now we bring the results back in to R.

embed0 <- as.matrix(py$embed_multi_clust0)
rownames(embed0) <- colnames(reclust_res[[1]])
colnames(embed0) <- c("Fit-SNE_1", "Fit-SNE_2")
reclust_res[[1]]@reductions$bh_tsne <- reclust_res[[1]]@reductions$tsne
reclust_res[[1]]@reductions$fitsne <- CreateDimReducObject(embeddings = embed0, 
                                                           key = "FitSNE_",
                                                           assay = "SCT", 
                                                           global = TRUE)
embed1 <- as.matrix(py$embed_multi_clust1)
rownames(embed1) <- colnames(reclust_res[[2]])
colnames(embed1) <- c("Fit-SNE_1", "Fit-SNE_2")
reclust_res[[2]]@reductions$bh_tsne <- reclust_res[[2]]@reductions$tsne
reclust_res[[2]]@reductions$fitsne <- CreateDimReducObject(embeddings = embed1,
                                                           key = "FitSNE_",
                                                           assay = "SCT",
                                                           global = TRUE)
embed2 <- as.matrix(py$embed_multi_clust2)
rownames(embed2) <- colnames(reclust_res[[3]])
colnames(embed2) <- c("Fit-SNE_1", "Fit-SNE_2")
reclust_res[[3]]@reductions$bh_tsne <- reclust_res[[3]]@reductions$tsne
reclust_res[[3]]@reductions$fitsne <- CreateDimReducObject(embeddings = embed2,
                                                           key = "FitSNE_",
                                                           assay = "SCT",
                                                           global = TRUE)

Here’s what our reclusterings look like. There’s clear visual separation between the main clusters and the subgroups we’ve discovered using SCISSORS.

p2 <- DimPlot(reclust_res[[1]], cols = paletteer_d("ggsci::default_locuszoom")) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      theme(legend.position = "bottom", 
            legend.justification = "center", 
            legend.direction = "horizontal") + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p3 <- DimPlot(reclust_res[[2]], cols = paletteer_d("ggsci::default_locuszoom")) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      theme(legend.position = "bottom", 
            legend.justification = "center", 
            legend.direction = "horizontal") + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p4 <- DimPlot(reclust_res[[3]], cols = paletteer_d("ggsci::default_locuszoom")) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      theme(legend.position = "bottom", 
            legend.justification = "center", 
            legend.direction = "horizontal") + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p2

p3

p4

Next, we’ll reintegrate our new clusters into our original Seurat object - this requires some finagling as Seurat is a bit weird with how it stores cell-level metadata. Since we had six clusters originally, and we discovered six new subclusters, we’ll end up with twelve total clusters.

clust_df <- data.frame(CellID = colnames(pbmc), 
                       ClustID = as.numeric(pbmc@meta.data$seurat_clusters) - 1, 
                       stringsAsFactors = FALSE)
clust_6 <- rownames(reclust_res[[1]]@meta.data[reclust_res[[1]]@meta.data$seurat_clusters == 1, ])
clust_7 <- rownames(reclust_res[[1]]@meta.data[reclust_res[[1]]@meta.data$seurat_clusters == 2, ])
clust_8 <- rownames(reclust_res[[1]]@meta.data[reclust_res[[1]]@meta.data$seurat_clusters == 3, ])
clust_9 <- rownames(reclust_res[[2]]@meta.data[reclust_res[[2]]@meta.data$seurat_clusters == 1, ])
clust_10 <- rownames(reclust_res[[3]]@meta.data[reclust_res[[3]]@meta.data$seurat_clusters == 1, ])
clust_11 <- rownames(reclust_res[[3]]@meta.data[reclust_res[[3]]@meta.data$seurat_clusters == 2, ])
label <- case_when(clust_df$CellID %in% clust_6 ~ 6, 
                   clust_df$CellID %in% clust_7 ~ 7, 
                   clust_df$CellID %in% clust_8 ~ 8, 
                   clust_df$CellID %in% clust_9 ~ 9, 
                   clust_df$CellID %in% clust_10 ~ 10, 
                   clust_df$CellID %in% clust_11 ~ 11, 
                   TRUE ~ clust_df$ClustID)
pbmc <- AddMetaData(pbmc, 
                    col.name = "seurat_clusters", 
                    metadata = as.factor(label))
Idents(pbmc) <- "seurat_clusters"
p5 <- DimPlot(pbmc, cols = cols25()) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      theme(legend.position = "bottom", 
            legend.justification = "center", 
            legend.direction = "horizontal") + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p5

Identify Cell Types

Now that we’ve determined our subpopulations, we can assign cell types to each cluster using the marker genes provided in the Satija Lab’s PBMC3k vignette, as well as other canonical markers.

CD4+ T Cells

We can quickly identify cluster 0 as the memory CD4+ T cells, and cluster 6 as the naive CD4+ population.

p6 <- FeaturePlot(pbmc, 
                  features = "IL7R", 
                  cols = c("lightgrey", "firebrick3")) + 
      NoLegend() + 
      theme_yehlab() + 
      theme(axis.title = element_blank())
p7 <- FeaturePlot(pbmc, 
                  features = "CCR7", 
                  cols = c("lightgrey", "firebrick3")) + 
      NoLegend() + 
      theme_yehlab()  + 
      theme(axis.title = element_blank())
p8 <- FeaturePlot(pbmc, 
                  features = "S100A4", 
                  cols = c("lightgrey", "firebrick3")) + 
      NoLegend() + 
      theme_yehlab() + 
      theme(axis.title = element_blank())
p9 <- (p6 | p7 | p8) / p5
p9

Cluster 7 is only subtly separated from the CD4+ T cell clusters. We’ll use differential expression testing to determine if the cells in cluster 7 are a spurious cluster or a real T cell subtype. After running a Wilcox test we can see that several of the differentially expressed are associated with the interferon family of cytokines and with anti-viral immune responses.

clust7_markers <- FindAllMarkers(pbmc, 
                                 assay = "SCT", 
                                 logfc.threshold = .5, 
                                 only.pos = TRUE, 
                                 test.use = "wilcox", 
                                 verbose = FALSE, 
                                 random.seed = 629) %>% 
                  subset(cluster  == 7 & p_val_adj < .05) 
clust7_markers
p_val avg_logFC pct.1 pct.2 p_val_adj cluster gene
IFIT1 0e+00 0.8411350 0.68 0.073 0.0000000 7 IFIT1
IFIT3 0e+00 0.8418490 0.72 0.099 0.0000000 7 IFIT3
RSAD2 0e+00 0.5548527 0.48 0.051 0.0000000 7 RSAD2
MX1 0e+00 0.9419429 0.84 0.197 0.0000000 7 MX1
ISG152 0e+00 1.6141024 1.00 0.424 0.0000000 7 ISG15
IFI61 0e+00 1.4006506 0.96 0.377 0.0000000 7 IFI6
LGALS3BP 0e+00 0.5142930 0.44 0.059 0.0000000 7 LGALS3BP
STAT1 0e+00 0.6117354 0.64 0.127 0.0000000 7 STAT1
OASL 0e+00 0.5655251 0.36 0.043 0.0000000 7 OASL
OAS1 0e+00 0.5748545 0.72 0.183 0.0000000 7 OAS1
ISG20 0e+00 0.9174060 0.96 0.433 0.0000000 7 ISG20
MT2A1 0e+00 1.0596818 0.80 0.295 0.0000001 7 MT2A
IFI44L 0e+00 1.0540589 0.76 0.246 0.0000009 7 IFI44L
IFITM11 0e+00 0.7859682 0.92 0.450 0.0000043 7 IFITM1
LY6E1 0e+00 0.7496914 0.92 0.658 0.0002478 7 LY6E
TNFSF101 1e-07 0.7812960 0.60 0.202 0.0008592 7 TNFSF10
MYL12A1 3e-07 0.5222692 1.00 0.881 0.0040999 7 MYL12A

Upon visual inspection of the top three marker genes (as determined by adjusted p-value), we can see that they do an equally good job of distinguishing the small cluster from the sample as a whole as they do at separating it from the memory CD4+ T cells. Due to their anti-viral characteristics , we’ll define this group as being composed of Type 1 helper T cells (Th1).

p10 <- FeaturePlot(pbmc, 
                   features = "IFIT1", 
                   cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p11 <- FeaturePlot(pbmc, 
                  features = "IFIT3", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p12 <- FeaturePlot(pbmc, 
                  features = "IFI6", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p13 <- (p10 | p11 | p12) / p5
p13

CD14+ Monocytes

Cluster 1 clearly houses our CD14+ monocytes.

p14 <- FeaturePlot(pbmc, 
                   features = "CD14", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p15 <- FeaturePlot(pbmc, 
                   features = "LYZ", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p16 <- (p14 | p15) / p5
p16

FCGR3A+ Monocytes

It follows that the FCGR3A+ monocytes are positioned next to the CD14+ monocytes in cluster 4.

p17 <- FeaturePlot(pbmc, 
                   features = "FCGR3A", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p18 <- FeaturePlot(pbmc, 
                   features = "MS4A7", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p19 <- (p17 | p18) / p5
p19

B Cells

Expression of MS4A1 allows us to isolate the B cells as belonging to cluster 3.

p20 <- FeaturePlot(pbmc, 
                   features = "MS4A1", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p21 <- p20 / p5
p21

CD8+ T Cells

The canonical marker CD8A swiftly identifies our CD8+ T cells in cluster 2.

p22 <- FeaturePlot(pbmc, 
                   features = "CD8A", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p23 <- p22 / p5
p23

Natural Killer Cells

We can use NKG7 and GNLY to isolate the NK cells in cluster 5.

p24 <- FeaturePlot(pbmc, 
                   features = "NKG7", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p25 <- FeaturePlot(pbmc, 
                   features = "GNLY", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p26 <- (p24 | p25) / p5
p26

Dendritic Cells

The dendritic cell group is defined by expression of FCER1A and CST3 in cluster 9.

p27 <- FeaturePlot(pbmc, 
                   features = "FCER1A", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p28 <- FeaturePlot(pbmc, 
                   features = "CST3", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p29 <- (p27 | p28) / p5
p29

Platelets

The tiny platelet population of 11 cells can be identified by its expression of PPBP in cluster 10.

p30 <- FeaturePlot(pbmc, 
                   features = "PPBP", 
                  cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p31 <- p30 / p5
p31

Plasmacytoid Dendritic Cells

A very small population of plasmacytoid dendritic cells - only 4 cells - was teased out by SCISSORS and can be annotated using expression of MZB1.

p32 <- FeaturePlot(pbmc, 
                   features = "MZB1", 
                   cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p33 <- p32 / p5
p33

Hematocytoblasts

Lastly, we need to assign an identity to the unknown cluster 8 - another rare cell population, also composed of only 4 cells. We’ll use differential expression to compare it as we did earlier with the Th1 cells. The marker CYTL1 is over-expressed in hematopoetic stem cells (hematocytoblasts).

clust8_markers <- FindAllMarkers(pbmc, 
                                 test.use = "wilcox", 
                                 only.pos = TRUE, 
                                 logfc.threshold = .75, 
                                 assay = "SCT", 
                                 verbose = FALSE, 
                                 random.seed = 629) %>% 
                  subset(cluster == 8 & p_val_adj < .05)
clust8_markers
p_val avg_logFC pct.1 pct.2 p_val_adj cluster gene
EGFL7 0e+00 0.8075975 0.75 0.003 0.0000000 8 EGFL7
CYTL1 0e+00 0.8057508 0.75 0.004 0.0000000 8 CYTL1
LZTS2 0e+00 2.2731955 0.50 0.004 0.0000000 8 LZTS2
RAB13 0e+00 0.7947415 0.75 0.015 0.0000000 8 RAB13
FCER1A 0e+00 1.1122867 0.75 0.019 0.0000000 8 FCER1A
SLC39A8 0e+00 0.7745096 0.75 0.036 0.0000000 8 SLC39A8
USP30 0e+00 1.8662538 0.25 0.006 0.0000020 8 USP30
IL1B 1e-07 0.9726204 0.75 0.075 0.0016298 8 IL1B
p34 <- FeaturePlot(pbmc, 
                   features = "CYTL1", 
                   cols = c("lightgrey", "firebrick3")) + 
       NoLegend() + 
       theme_yehlab() + 
       theme(axis.title = element_blank())
p35 <- p34 / p5
p35

Final Figure

Finally, we’ll add cell labels to our original Seurat object and plot the results.

pbmc@meta.data$label <- case_when(pbmc@meta.data$seurat_clusters == 0 ~ "Memory CD4+ T", 
                                  pbmc@meta.data$seurat_clusters == 1 ~ "CD14+ Monocyte", 
                                  pbmc@meta.data$seurat_clusters == 2 ~ "CD8+ T", 
                                  pbmc@meta.data$seurat_clusters == 3 ~ "B", 
                                  pbmc@meta.data$seurat_clusters == 4 ~ "FCGR3A+ Monocyte", 
                                  pbmc@meta.data$seurat_clusters == 5 ~ "NK", 
                                  pbmc@meta.data$seurat_clusters == 6 ~ "Naive CD4+ T", 
                                  pbmc@meta.data$seurat_clusters == 7 ~ "Th1", 
                                  pbmc@meta.data$seurat_clusters == 8 ~ "Hematocytoblast", 
                                  pbmc@meta.data$seurat_clusters == 9 ~ "DC", 
                                  pbmc@meta.data$seurat_clusters == 10 ~ "Platelet", 
                                  pbmc@meta.data$seurat_clusters == 11 ~ "Plasmacytoid DC")

We redefine the final color palette to be used in order to have a better-looking figure. Note: if you’re manually defining colors and want Seurat to assign a certain color to a certain cell type or cluster number, you need to name the vector of colors you provide.

final_pal <- c("paleturquoise4", "goldenrod", "steelblue1", "lightseagreen", 
               "sienna4", "mediumblue", "coral3", "magenta", "limegreen", 
               "forestgreen", "darkorchid3", "orange")
names(final_pal) <- c("Memory CD4+ T", "CD14+ Monocyte", "CD8+ T", "B", 
                      "FCGR3A+ Monocyte", "NK", "Naive CD4+ T", "Th1", 
                      "Hematocytoblast", "DC", "Platelet", "Plasmacytoid DC")
p36 <- DimPlot(pbmc, cols = final_pal, group.by = "label") + 
       theme_yehlab() + 
       theme(legend.position = "bottom", 
             legend.justification = "center", 
             legend.text = element_text(size = 11)) + 
       guides(color = guide_legend(nrow = 3, override.aes = list(size = 3))) + 
       labs(x = "Fit-SNE 1", y = "Fit-SNE 2", title = NULL)
p36

Conclusions

SCISSORS revealed tiny platelet and plasmacytoid dendritic cell clusters that were initially grouped with the CD8+ T cells, and it helped us to separate the dendritic cells from the larger CD14+ monocyte cluster. It also split up the naive and memory CD4+ T cells, and showed us a tiny Th1 cell subset that was not initially visible. The plasmacytoid DCs and Th1 cells were not annotated in the original Satija Lab PBMC3k vignette.

We used the PBMC3k dataset because of 1) its immediate availability to anyone wishing to replicate our results and 2) the validity of its annotations, which allowed us to be confident in the results from SCISSORS, which was able to carve out rare cell groups from larger, broader cell types. In this case, the dendritic cell cluster was composed of 31 cells, the platelet cluster of 11 cells, the Th1 cluster of 25 cells, and the minuscule plasmacytoid DC cluster of just 4 cells. Respectively, these cell types made up 1.15%, 0.41%, 0.93%, and 0.15% of the entire sample. We thus believe we can confidently say that SCISSORS has been shown to accurately and swiftly identify rare cell types by considering the variance in gene expression within clusters and judging iterative reclustering using silhouette scores, rather than attempting to identify rare cell populations at the level of the entire dataset.

Save Data & Figures

This part isn’t really worth reading; it’s just here to prove that all the figures were actually dynamically generated and saved upon knitting this document.

pdrive_dir <- "/Volumes/labs/Home/Jen Jen Yeh Lab/Jack/Reclustering Project/Seurat Objects/pbmc3k_202101.Rds"
saveRDS(pbmc, file = pdrive_dir)

We’ll create a quick convenience function to help us save the figures.

saveSCISSORS <- function(plot = NULL, 
                         name = NULL, 
                         border = TRUE, 
                         pub.ready = FALSE) {
  if (is.null(plot) | is.null(name)) stop("You forgot some arguments.")
  if (pub.ready) {
    dir <- "~/Desktop/R/SCISSORS/vignettes/figures_pub/PBMC"
    if (!border) {
      plot <- plot + 
              theme(panel.border = element_blank(), 
                    axis.title = element_blank(), 
                    legend.position = "none")
    } else {
      plot <- plot + 
              theme(axis.title = element_blank(), 
                    legend.position = "none")
    }
    ggsave(filename = paste0(name, ".pdf"), 
           device = "pdf", 
           units = "in",
           path = dir, 
           height = 8, 
           width = 8) 
  } else {
    dir <- "~/Desktop/R/SCISSORS/vignettes/figures_supp/PBMC"
    ggsave(filename = paste0(name, ".pdf"), 
           device = "pdf", 
           units = "in",
           path = dir, 
           height = 8, 
           width = 8) 
  }
}

Lastly, we’ll save the figures under ./vignettes/figures/.

saveSCISSORS(plot = p0, name = "Seurat_Clusters", 
             pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p1, name = "Seurat_Clusters_FitSNE", 
             pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p2, name = "Clust0_Reclust", 
             pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p3, name = "Clust1_Reclust", 
             pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p4, name = "Clust2_Reclust", 
             pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p5, name = "SCISSORS_Clusters", 
             pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p6, name = "CD4T_IL7R")
saveSCISSORS(plot = p7, name = "CD4T_CCR7")
saveSCISSORS(plot = p8, name = "CD4T_S100A4")
saveSCISSORS(plot = p10, name = "TH1_IFIT1")
saveSCISSORS(plot = p11, name = "TH1_IFIT3")
saveSCISSORS(plot = p12, name = "TH1_IFI6")
saveSCISSORS(plot = p14, name = "Monocyte_CD14")
saveSCISSORS(plot = p15, name = "Monocyte_LYZ")
saveSCISSORS(plot = p17, name = "FCGR3A_Monocyte_FCGR3A")
saveSCISSORS(plot = p18, name = "FCGR3A_Monocyte_MS4A7")
saveSCISSORS(plot = p20, name = "B_MS4A1")
saveSCISSORS(plot = p22, name = "CD8T_CD8A")
saveSCISSORS(plot = p24, name = "NK_NKG7")
saveSCISSORS(plot = p25, name = "NK_GNLY")
saveSCISSORS(plot = p27, name = "DC_FCER1A")
saveSCISSORS(plot = p28, name = "DC_CST3")
saveSCISSORS(plot = p30, name = "Platelet_PPBP")
saveSCISSORS(plot = p32, name = "Plasmacytoid_DC_MZB1")
saveSCISSORS(plot = p34, name = "Hematocytoblasts_CYTL1")
saveSCISSORS(plot = p36, name = "SCISSORS_Final_Annotations", 
             pub.ready = TRUE, border = FALSE)

And of course:

sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Catalina 10.15.7
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## attached base packages:
## [1] parallel  stats4    stats     graphics  grDevices utils     datasets 
## [8] methods   base     
## 
## other attached packages:
##  [1] pbmc3k.SeuratData_3.1.4     SeuratData_0.2.1           
##  [3] reticulate_1.18             paletteer_1.3.0            
##  [5] SCISSORS_0.0.2.0            SingleCellExperiment_1.10.1
##  [7] SummarizedExperiment_1.18.2 DelayedArray_0.14.1        
##  [9] matrixStats_0.57.0          Biobase_2.48.0             
## [11] GenomicRanges_1.40.0        GenomeInfoDb_1.24.2        
## [13] IRanges_2.22.2              S4Vectors_0.26.1           
## [15] BiocGenerics_0.34.0         data.table_1.13.6          
## [17] cluster_2.1.0               biomaRt_2.44.1             
## [19] ggplot2_3.3.3               Seurat_3.2.3               
## [21] dplyr_1.0.2                 pals_1.6                   
## 
## loaded via a namespace (and not attached):
##   [1] BiocFileCache_1.12.1   plyr_1.8.6             igraph_1.2.6          
##   [4] lazyeval_0.2.2         splines_4.0.3          listenv_0.8.0         
##   [7] scattermore_0.7        digest_0.6.27          htmltools_0.5.0       
##  [10] fansi_0.4.1            magrittr_2.0.1         memoise_1.1.0         
##  [13] tensor_1.5             ROCR_1.0-11            limma_3.44.3          
##  [16] globals_0.14.0         askpass_1.1            prettyunits_1.1.1     
##  [19] colorspace_2.0-0       blob_1.2.1             rappdirs_0.3.1        
##  [22] ggrepel_0.9.0          xfun_0.19              prismatic_1.0.0       
##  [25] crayon_1.3.4           RCurl_1.98-1.2         jsonlite_1.7.2        
##  [28] spatstat_1.64-1        spatstat.data_1.7-0    survival_3.2-7        
##  [31] zoo_1.8-8              glue_1.4.2             polyclip_1.10-0       
##  [34] gtable_0.3.0           zlibbioc_1.34.0        XVector_0.28.0        
##  [37] leiden_0.3.6           future.apply_1.7.0     maps_3.3.0            
##  [40] abind_1.4-5            scales_1.1.1           DBI_1.1.0             
##  [43] miniUI_0.1.1.1         Rcpp_1.0.5             viridisLite_0.3.0     
##  [46] xtable_1.8-4           progress_1.2.2         bit_4.0.4             
##  [49] rsvd_1.0.3             mapproj_1.2.7          htmlwidgets_1.5.3     
##  [52] httr_1.4.2             RColorBrewer_1.1-2     ellipsis_0.3.1        
##  [55] ica_1.0-2              farver_2.0.3           pkgconfig_2.0.3       
##  [58] XML_3.99-0.5           uwot_0.1.10            dbplyr_2.0.0          
##  [61] deldir_0.2-3           labeling_0.4.2         tidyselect_1.1.0      
##  [64] rlang_0.4.10           reshape2_1.4.4         later_1.1.0.1         
##  [67] AnnotationDbi_1.50.3   munsell_0.5.0          tools_4.0.3           
##  [70] cli_2.2.0              generics_0.1.0         RSQLite_2.2.1         
##  [73] ggridges_0.5.2         evaluate_0.14          stringr_1.4.0         
##  [76] fastmap_1.0.1          yaml_2.2.1             goftest_1.2-2         
##  [79] rematch2_2.1.2         knitr_1.30             bit64_4.0.5           
##  [82] fitdistrplus_1.1-3     purrr_0.3.4            RANN_2.6.1            
##  [85] pbapply_1.4-3          future_1.21.0          nlme_3.1-151          
##  [88] mime_0.9               rstudioapi_0.13        compiler_4.0.3        
##  [91] plotly_4.9.2.2         curl_4.3               png_0.1-7             
##  [94] spatstat.utils_1.17-0  tibble_3.0.4           stringi_1.5.3         
##  [97] highr_0.8              lattice_0.20-41        Matrix_1.3-0          
## [100] vctrs_0.3.6            pillar_1.4.7           lifecycle_0.2.0       
## [103] lmtest_0.9-38          RcppAnnoy_0.0.18       cowplot_1.1.1         
## [106] bitops_1.0-6           irlba_2.3.3            httpuv_1.5.4          
## [109] patchwork_1.1.1        R6_2.5.0               promises_1.1.1        
## [112] KernSmooth_2.23-18     gridExtra_2.3          parallelly_1.23.0     
## [115] codetools_0.2-18       dichromat_2.0-0        MASS_7.3-53           
## [118] assertthat_0.2.1       openssl_1.4.3          withr_2.3.0           
## [121] sctransform_0.3.2      GenomeInfoDbData_1.2.3 mgcv_1.8-33           
## [124] hms_0.5.3              grid_4.0.3             rpart_4.1-15          
## [127] tidyr_1.1.2            rmarkdown_2.6          Rtsne_0.15            
## [130] shiny_1.5.0
LS0tCnRpdGxlOiAiUEJNQyBBbmFseXNpcyB1c2luZyBTQ0lTU09SUyIKc3VidGl0bGU6ICJKYWNrIExlYXJ5IgphdXRob3I6IAogIC0gIlVuaXZlcnNpdHkgb2YgTm9ydGggQ2Fyb2xpbmEgYXQgQ2hhcGVsIEhpbGwgLSBMaW5lYmVyZ2VyIENvbXByZWhlbnNpdmUgQ2FuY2VyIENlbnRlciIKICAtICJVbml2ZXJzaXR5IG9mIEZsb3JpZGEgLSBEZXBhcnRtZW50IG9mIEJpb3N0YXRpc3RpY3MiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogcGFwZXIKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGRmX3ByaW50OiBrYWJsZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAiY2VudGVyIikKcmV0aWN1bGF0ZTo6dXNlX3ZpcnR1YWxlbnYoIn4vRGVza3RvcC9QeXRob24vc2NpZW5jZS92ZW52LyIsIHJlcXVpcmVkID0gVFJVRSkKYGBgCgojIEludHJvZHVjdGlvbgoKVGhpcyBkb2N1bWVudCB3aWxsIHNlcnZlIGFzIGEgdHV0b3JpYWwgZm9yIHVzaW5nIGBTQ0lTU09SU2AsIHdpdGggdGhlIGFkZGVkIGZ1bmN0aW9uYWxpdHkgb2YgZGV0YWlsaW5nIGV4YWN0bHkgaG93IHRoZSBQQk1DM2sgZGF0YXNldCBmaWd1cmVzIHByZXNlbnRlZCBpbiBvdXIgbWFudXNjcmlwdCB3ZXJlIGdlbmVyYXRlZC4gV2UnbGwgc3RhcnQgZnJvbSBhIDEwWCBjb3VudHMgbWF0cml4IGFuZCBlbmQgd2l0aCBmdWxseSBhbm5vdGF0ZWQgY2VsbCBjbHVzdGVycy4gSW4gYWRkaXRpb24gdG8gUiwgaW4gb3JkZXIgdG8gcnVuIGFsbCB0aGUgY29kZSBpbiB0aGlzIGRvY3VtZW50IHlvdSdsbCBuZWVkIGEgUHl0aG9uIDMgaW5zdGFsbGF0aW9uIHdpdGggYG9wZW5UU05FYCBhbmQgYWxsIGl0cyBkZXBlbmRlbmNpZXMgaW5zdGFsbGVkLgoKIyBMaWJyYXJpZXMKCmBgYHtyfQpsaWJyYXJ5KHBhbHMpICAgICAgICAjIGJhc2ljIGNvbG9ycwpsaWJyYXJ5KGRwbHlyKSAgICAgICAjIHRpZHkgZGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShTZXVyYXQpICAgICAgIyBzaW5nbGUgY2VsbCBpbmZyYXN0cnVjdHVyZQpsaWJyYXJ5KGdncGxvdDIpICAgICAjIHBsb3RzCmxpYnJhcnkoU0NJU1NPUlMpICAgICMgb3VyIHBhY2thZ2UgCmxpYnJhcnkocGFsZXR0ZWVyKSAgICMgYWR2YW5jZWQgY29sb3JzCmxpYnJhcnkocmV0aWN1bGF0ZSkgICMgUHl0aG9uIGludGVyZmFjZQpsaWJyYXJ5KFNldXJhdERhdGEpICAjIFBCTUMzayBkYXRhc2V0CmBgYAoKIyBEYXRhCgpXZSBsb2FkIGEgc2NSTkEtc2VxIGRhdGFzZXQgcHJvdmlkZWQgYnkgMTBYIEdlbm9taWNzIHRoYXQgY29uc2lzdHMgb2YgMiw3MDAgcGVyaXBoZXJhbCBibG9vZCBtb25vbnVjbGVhciBjZWxscyAoUEJNQ3MpIGZyb20gYSBoZWFsdGh5IGRvbm9yLgoKYGBge3J9CnBibWMgPC0gTG9hZERhdGEoInBibWMzayIpCmBgYAoKIyBQcmVwcm9jZXNzaW5nCgpIZXJlIHdlIHVzZSBgUHJlcGFyZURhdGEoKWAgdG8gbm9ybWFsaXplIGV4cHJlc3Npb24gJiBzZWxlY3QgaGlnaGx5IHZhcmlhYmxlIGdlbmVzIHRocm91Z2ggYHNjdHJhbnNmb3JtYCwgcnVuIFBDQSAmIHQtU05FLiBhbmQgY2x1c3RlciBvdXIgY2VsbHMuIFdlIHV0aWxpemUgMTUgcHJpbmNpcGFsIGNvbXBvbmVudHMgZXZlbiB0aG91Z2ggW3RoZSBTYXRpamEgTGFiIHZpZ25ldHRlXSgoaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC92My4yL3BibWMza190dXRvcmlhbC5odG1sKSkgdXNlZCAxMCwgYXMgd2UgdXNlIGBzY3RyYW5zZm9ybWAgbm9ybWFsaXphdGlvbiwgd2hpY2ggZG9lcyBhIGJldHRlciBqb2Igb2YgcmV0YWluaW5nIGJpb2xvZ2ljYWwgaGV0ZXJvZ2VuZWl0eSB0aHJvdWdoIG5vcm1hbGl6YXRpb24gdGhhbiBzdGFuZGFyZCBsb2ctbm9ybWFsaXphdGlvbi4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpwYm1jIDwtIFByZXBhcmVEYXRhKHBibWMsIAogICAgICAgICAgICAgICAgICAgIG4udmFyaWFibGUuZ2VuZXMgPSA0MDAwLCAKICAgICAgICAgICAgICAgICAgICBuLlBDID0gMTUsIAogICAgICAgICAgICAgICAgICAgIHdoaWNoLmRpbS5yZWR1YyA9ICJ0c25lIiwKICAgICAgICAgICAgICAgICAgICBpbml0aWFsLnJlc29sdXRpb24gPSAuNCwgCiAgICAgICAgICAgICAgICAgICAgcmFuZG9tLnNlZWQgPSA2MjkpCnAwIDwtIERpbVBsb3QocGJtYywgY29scyA9IGNvbHMyNSgpKSArIAogICAgICBsYWJzKHggPSAidC1TTkUgMSIsIHkgPSAidC1TTkUgMiIpICsgCiAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCAKICAgICAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSAiY2VudGVyIiwgCiAgICAgICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpICsgCiAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMSwgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKQpwMApgYGAKCiMjIEZpdC1TTkUKCldlJ2xsIGFsc28gcnVuIHRoZSBGYXN0IEZvdXJpZXIgVHJhbnNmb3JtLWFjY2VsZXJhdGVkIHZlcnNpb24gb2YgdC1TTkUgYXMgaW1wbGVtZW50ZWQgaW4gdGhlIFB5dGhvbiBsaWJyYXJ5IGBvcGVuVFNORWAuIEZpcnN0IHdlJ2xsIG5lZWQgdG8gZ2V0IG91ciBQQyBtYXRyaXggaW50byBhIGZvcm0gYWNjZXNzaWJsZSBieSBvdXIgUHl0aG9uIGludGVycHJldGVyLgoKYGBge3J9CnBjX21hdCA8LSBFbWJlZGRpbmdzKHBibWMsIHJlZHVjdGlvbiA9ICJwY2EiKQpgYGAKCmBgYHtweXRob259CiMgaW1wb3J0IGxpYnJhcmllcwppbXBvcnQgbnVtcHkgYXMgbnAKaW1wb3J0IHBhbmRhcyBhcyBwZApmcm9tIG9wZW5UU05FIGltcG9ydCBUU05FRW1iZWRkaW5nCmZyb20gb3BlblRTTkUgaW1wb3J0IGluaXRpYWxpemF0aW9uCmZyb20gb3BlblRTTkUuYWZmaW5pdHkgaW1wb3J0IE11bHRpc2NhbGUKIyBpbXBvcnQgUEMgbWF0cml4CnBjX21hdCA9IG5wLmFycmF5KHIucGNfbWF0KQojIHJ1biBGaXQtU05FIHcvIG11bHRpc2NhbGUga2VybmVsCmFmZmluX211bHRpID0gTXVsdGlzY2FsZShwY19tYXQsIHBlcnBsZXhpdGllcz1bMzAsIDE1MF0sIG1ldHJpYz0nY29zaW5lJywgcmFuZG9tX3N0YXRlPTYyOSkKaW5pdCA9IGluaXRpYWxpemF0aW9uLnBjYShwY19tYXQsIHJhbmRvbV9zdGF0ZT02MjkpCnRzbmVfb2JqID0gVFNORUVtYmVkZGluZyhpbml0LCBhZmZpbl9tdWx0aSwgbmVnYXRpdmVfZ3JhZGllbnRfbWV0aG9kPSdmZnQnKQplbWJlZCA9IHRzbmVfb2JqLm9wdGltaXplKG5faXRlcj0zMDAsIGV4YWdnZXJhdGlvbj0xMiwgbW9tZW50dW09MC42KQplbWJlZF9tdWx0aSA9IGVtYmVkLm9wdGltaXplKG5faXRlcj02MDAsIGV4YWdnZXJhdGlvbj0xLCBtb21lbnR1bT0wLjgpCmBgYAoKTm93IHdlIHB1bGwgdGhlIHJlc3VsdHMgYmFjayBpbnRvIFIsIGFuZCBzYXZlIHRoZW0gaW4gb3VyIGBTZXVyYXRgIG9iamVjdC4gV2Ugc2F2ZSB0aGUgb3JpZ2luYWwgZW1iZWRkaW5nIChtYWRlIHVzaW5nIHRoZSBkZWZhdWx0IEJhcm5lcy1IdXQgdC1TTkUgaW1wbGVtZW50YXRpb24pIGluIGBwYm1jQHJlZHVjdGlvbiRiaF90c25lYCAtIHdlIG5lZWQgdG8gZG8gdGhpcyBpbiBvcmRlciB0byBtYWtlIHRoZSBGaXQtU05FIGVtYmVkZGluZyB0aGUgZGVmYXVsdCBlbWJlZGRpbmcgdGhhdCB3aWxsIGJlIHJldHJpZXZlZCBpbiBjYWxscyB0byBmdW5jdGlvbnMgc3VjaCBhcyBgRGltUGxvdCgpYCBvciBgRmVhdHVyZVBsb3QoKWAuIAoKYGBge3J9CmVtYmVkIDwtIGFzLm1hdHJpeChweSRlbWJlZF9tdWx0aSkKcm93bmFtZXMoZW1iZWQpIDwtIGNvbG5hbWVzKHBibWMpCmNvbG5hbWVzKGVtYmVkKSA8LSBjKCJGaXQtU05FXzEiLCAiRml0LVNORV8yIikKcGJtY0ByZWR1Y3Rpb25zJGJoX3RzbmUgPC0gcGJtY0ByZWR1Y3Rpb25zJHRzbmUKcGJtY0ByZWR1Y3Rpb25zJHRzbmUgPC0gQ3JlYXRlRGltUmVkdWNPYmplY3QoZW1iZWRkaW5ncyA9IGVtYmVkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5ID0gIkZpdFNORV8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiU0NUIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdsb2JhbCA9IFRSVUUpCmBgYAoKVmlzdWFsaXppbmcgdGhlIHJlc3VsdHMgc2hvd3MgcHJldHR5IG11Y2ggdGhlIHNhbWUgZ2xvYmFsIHN0cnVjdHVyZSBhcyB3aXRoIHRoZSBkZWZhdWx0IHQtU05FIGltcGxlbWVudGF0aW9uLCBhbGJlaXQgcm90YXRlZCBhIGJpdCwgYnV0IEkgbGlrZSB0aGF0IEZpdC1TTkUncyBjbHVzdGVycyBhcmUgYSBiaXQgZGVuc2VyLCBzbyB3ZSdsbCB1c2UgRml0LVNORSBnb2luZyBmb3J3YXJkLgoKYGBge3J9CnAxIDwtIERpbVBsb3QocGJtYywgY29scyA9IGNvbHMyNSgpKSArIAogICAgICBsYWJzKHggPSAiRml0LVNORSAxIiwgeSA9ICJGaXQtU05FIDIiKSArIAogICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgCiAgICAgICAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImNlbnRlciIsIAogICAgICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKSArIAogICAgICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQobnJvdyA9IDEsIG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDMpKSkKcDEKYGBgCgojIFJlY2x1c3RlcmluZwoKSGVyZSB3ZSdsbCBleGFtaW5lIGNsdXN0ZXJzIDAsIDEsIGFuZCAyLiBDbHVzdGVyIDAgc2VlbXMgbGFyZ2UgZW5vdWdoLCBhbmQgaGFzIGVub3VnaCB2YXJpYWJpbGl0eSBvbiB0aGUgWC1heGlzIG9mIHRoZSB0LVNORSBlbWJlZGRpbmcgdG8gYXBwZWFyIGFzIHRob3VnaCBpdCBtaWdodCBoYXJib3Igc3ViZ3JvdXBzLiBDbHVzdGVycyAxIGFuZCAyIGhhdmUgc21hbGwgYnV0IHZpc2libGUgc3ViY2x1c3RlcnMuCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naG9sZCd9CnJlY2x1c3RfcmVzIDwtIFJlY2x1c3RlckNlbGxzKHBibWMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aGljaC5jbHVzdCA9IGxpc3QoMCwgMSwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuLnZhcmlhYmxlLmdlbmVzID0gNDAwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbi5QQyA9IDE1LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgay52YWxzID0gYyg1LCAxMCwgMTUsIDIwLCAyNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXNvbHV0aW9uLnZhbHMgPSBjKC4xLCAuMiwgLjMsIC40KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoaWNoLmRpbS5yZWR1YyA9ICJ0c25lIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZG8uZW1iZWRkaW5nID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbS5zZWVkID0gNjI5KQpgYGAKCk5vdyB3ZSdsbCBydW4gRml0LVNORSBvbiBlYWNoIG9mIHRoZSByZWNsdXN0ZXJlZCBvYmplY3RzIGZvciBjb25zaXN0ZW5jaWVzIHNha2UuIEZpcnN0IHdlJ2xsIG5lZWQgdG8gaXNvbGF0ZSB0aGUgUEMgbWF0cmljZXMgYW5kIHNlbmQgdGhlbSB0byBQeXRob24uIAoKYGBge3J9CnBjX2NsdXN0MCA8LSBFbWJlZGRpbmdzKHJlY2x1c3RfcmVzW1sxXV0sIHJlZHVjdGlvbiA9ICJwY2EiKQpwY19jbHVzdDEgPC0gRW1iZWRkaW5ncyhyZWNsdXN0X3Jlc1tbMl1dLCByZWR1Y3Rpb24gPSAicGNhIikKcGNfY2x1c3QyIDwtIEVtYmVkZGluZ3MocmVjbHVzdF9yZXNbWzNdXSwgcmVkdWN0aW9uID0gInBjYSIpCmBgYAoKUnVubmluZyBGaXQtU05FIHRocmVlIHRpbWVzIGNvdWxkIHByb2JhYmx5IGJlIGNsZWFuZWQgdXAgYW5kIGRvbmUgaW4gYSBmb3IgbG9vcCwgYnV0IHRoaXMgd2FzIGVhc2llc3QgdG8gd3JpdGUgb3V0IGluIGEgc2hvcnQgdGltZS4gV2UgdXNlIGRpZmZlcmVudCBwZXJwbGV4aXR5IHNldHMgZm9yIGVhY2ggY2x1c3RlciwgYXMgdGhleSdyZSBhbGwgY29tcG9zZWQgb2YgZGlmZmVyaW5nIG51bWJlcnMgb2YgY2VsbHMuCgpgYGB7cHl0aG9ufQojIGltcG9ydCBQQyBtYXRyaWNlcwpwY19jbHVzdDAgPSBucC5hcnJheShyLnBjX2NsdXN0MCkKcGNfY2x1c3QxID0gbnAuYXJyYXkoci5wY19jbHVzdDEpCnBjX2NsdXN0MiA9IG5wLmFycmF5KHIucGNfY2x1c3QyKQoKIyBydW4gRml0LVNORSB3LyBtdWx0aXNjYWxlIGtlcm5lbCAtIGNsdXN0ZXIgMAphZmZpbl9tdWx0aV9jbHVzdDAgPSBNdWx0aXNjYWxlKHBjX2NsdXN0MCwgcGVycGxleGl0aWVzPVsxNSwgNTBdLCBtZXRyaWM9J2Nvc2luZScsIHJhbmRvbV9zdGF0ZT02MjkpCmluaXRfY2x1c3QwID0gaW5pdGlhbGl6YXRpb24ucGNhKHBjX2NsdXN0MCwgcmFuZG9tX3N0YXRlPTYyOSkKdHNuZV9vYmpfY2x1c3QwID0gVFNORUVtYmVkZGluZyhpbml0X2NsdXN0MCwgYWZmaW5fbXVsdGlfY2x1c3QwLCBuZWdhdGl2ZV9ncmFkaWVudF9tZXRob2Q9J2ZmdCcpCmVtYmVkX2NsdXN0MCA9IHRzbmVfb2JqX2NsdXN0MC5vcHRpbWl6ZShuX2l0ZXI9MzAwLCBleGFnZ2VyYXRpb249MTIsIG1vbWVudHVtPTAuNykKZW1iZWRfbXVsdGlfY2x1c3QwID0gZW1iZWRfY2x1c3QwLm9wdGltaXplKG5faXRlcj04NTAsIGV4YWdnZXJhdGlvbj0xLCBtb21lbnR1bT0wLjgpCgojIHJ1biBGaXQtU05FIHcvIG11bHRpc2NhbGUga2VybmVsIC0gY2x1c3RlciAxCmFmZmluX211bHRpX2NsdXN0MSA9IE11bHRpc2NhbGUocGNfY2x1c3QxLCBwZXJwbGV4aXRpZXM9WzE1LCAzMF0sIG1ldHJpYz0nY29zaW5lJywgcmFuZG9tX3N0YXRlPTYyOSkKaW5pdF9jbHVzdDEgPSBpbml0aWFsaXphdGlvbi5wY2EocGNfY2x1c3QxLCByYW5kb21fc3RhdGU9NjI5KQp0c25lX29ial9jbHVzdDEgPSBUU05FRW1iZWRkaW5nKGluaXRfY2x1c3QxLCBhZmZpbl9tdWx0aV9jbHVzdDEsIG5lZ2F0aXZlX2dyYWRpZW50X21ldGhvZD0nZmZ0JykKZW1iZWRfY2x1c3QxID0gdHNuZV9vYmpfY2x1c3QxLm9wdGltaXplKG5faXRlcj0zMDAsIGV4YWdnZXJhdGlvbj0xMiwgbW9tZW50dW09MC42KQplbWJlZF9tdWx0aV9jbHVzdDEgPSBlbWJlZF9jbHVzdDEub3B0aW1pemUobl9pdGVyPTg1MCwgZXhhZ2dlcmF0aW9uPTEsIG1vbWVudHVtPTAuOCkKCiMgcnVuIEZpdC1TTkUgdy8gbXVsdGlzY2FsZSBrZXJuZWwgLSBjbHVzdGVyIDIKYWZmaW5fbXVsdGlfY2x1c3QyID0gTXVsdGlzY2FsZShwY19jbHVzdDIsIHBlcnBsZXhpdGllcz1bMTAsIDMwXSwgbWV0cmljPSdjb3NpbmUnLCByYW5kb21fc3RhdGU9NjI5KQppbml0X2NsdXN0MiA9IGluaXRpYWxpemF0aW9uLnBjYShwY19jbHVzdDIsIHJhbmRvbV9zdGF0ZT02MjkpCnRzbmVfb2JqX2NsdXN0MiA9IFRTTkVFbWJlZGRpbmcoaW5pdF9jbHVzdDIsIGFmZmluX211bHRpX2NsdXN0MiwgbmVnYXRpdmVfZ3JhZGllbnRfbWV0aG9kPSdmZnQnKQplbWJlZF9jbHVzdDIgPSB0c25lX29ial9jbHVzdDIub3B0aW1pemUobl9pdGVyPTMwMCwgZXhhZ2dlcmF0aW9uPTEyLCBtb21lbnR1bT0wLjYpCmVtYmVkX211bHRpX2NsdXN0MiA9IGVtYmVkX2NsdXN0Mi5vcHRpbWl6ZShuX2l0ZXI9NzUwLCBleGFnZ2VyYXRpb249MSwgbW9tZW50dW09MC44KQpgYGAKCk5vdyB3ZSBicmluZyB0aGUgcmVzdWx0cyBiYWNrIGluIHRvIFIuCgpgYGB7cn0KZW1iZWQwIDwtIGFzLm1hdHJpeChweSRlbWJlZF9tdWx0aV9jbHVzdDApCnJvd25hbWVzKGVtYmVkMCkgPC0gY29sbmFtZXMocmVjbHVzdF9yZXNbWzFdXSkKY29sbmFtZXMoZW1iZWQwKSA8LSBjKCJGaXQtU05FXzEiLCAiRml0LVNORV8yIikKcmVjbHVzdF9yZXNbWzFdXUByZWR1Y3Rpb25zJGJoX3RzbmUgPC0gcmVjbHVzdF9yZXNbWzFdXUByZWR1Y3Rpb25zJHRzbmUKcmVjbHVzdF9yZXNbWzFdXUByZWR1Y3Rpb25zJGZpdHNuZSA8LSBDcmVhdGVEaW1SZWR1Y09iamVjdChlbWJlZGRpbmdzID0gZW1iZWQwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXkgPSAiRml0U05FXyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiU0NUIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2xvYmFsID0gVFJVRSkKZW1iZWQxIDwtIGFzLm1hdHJpeChweSRlbWJlZF9tdWx0aV9jbHVzdDEpCnJvd25hbWVzKGVtYmVkMSkgPC0gY29sbmFtZXMocmVjbHVzdF9yZXNbWzJdXSkKY29sbmFtZXMoZW1iZWQxKSA8LSBjKCJGaXQtU05FXzEiLCAiRml0LVNORV8yIikKcmVjbHVzdF9yZXNbWzJdXUByZWR1Y3Rpb25zJGJoX3RzbmUgPC0gcmVjbHVzdF9yZXNbWzJdXUByZWR1Y3Rpb25zJHRzbmUKcmVjbHVzdF9yZXNbWzJdXUByZWR1Y3Rpb25zJGZpdHNuZSA8LSBDcmVhdGVEaW1SZWR1Y09iamVjdChlbWJlZGRpbmdzID0gZW1iZWQxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSA9ICJGaXRTTkVfIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJTQ1QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdsb2JhbCA9IFRSVUUpCmVtYmVkMiA8LSBhcy5tYXRyaXgocHkkZW1iZWRfbXVsdGlfY2x1c3QyKQpyb3duYW1lcyhlbWJlZDIpIDwtIGNvbG5hbWVzKHJlY2x1c3RfcmVzW1szXV0pCmNvbG5hbWVzKGVtYmVkMikgPC0gYygiRml0LVNORV8xIiwgIkZpdC1TTkVfMiIpCnJlY2x1c3RfcmVzW1szXV1AcmVkdWN0aW9ucyRiaF90c25lIDwtIHJlY2x1c3RfcmVzW1szXV1AcmVkdWN0aW9ucyR0c25lCnJlY2x1c3RfcmVzW1szXV1AcmVkdWN0aW9ucyRmaXRzbmUgPC0gQ3JlYXRlRGltUmVkdWNPYmplY3QoZW1iZWRkaW5ncyA9IGVtYmVkMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXkgPSAiRml0U05FXyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiU0NUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnbG9iYWwgPSBUUlVFKQpgYGAKCkhlcmUncyB3aGF0IG91ciByZWNsdXN0ZXJpbmdzIGxvb2sgbGlrZS4gVGhlcmUncyBjbGVhciB2aXN1YWwgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZSBtYWluIGNsdXN0ZXJzIGFuZCB0aGUgc3ViZ3JvdXBzIHdlJ3ZlIGRpc2NvdmVyZWQgdXNpbmcgYFNDSVNTT1JTYC4KCmBgYHtyfQpwMiA8LSBEaW1QbG90KHJlY2x1c3RfcmVzW1sxXV0sIGNvbHMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmRlZmF1bHRfbG9jdXN6b29tIikpICsgCiAgICAgIGxhYnMoeCA9ICJGaXQtU05FIDEiLCB5ID0gIkZpdC1TTkUgMiIpICsgCiAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCAKICAgICAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSAiY2VudGVyIiwgCiAgICAgICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpICsgCiAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMSwgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKQpwMyA8LSBEaW1QbG90KHJlY2x1c3RfcmVzW1syXV0sIGNvbHMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmRlZmF1bHRfbG9jdXN6b29tIikpICsgCiAgICAgIGxhYnMoeCA9ICJGaXQtU05FIDEiLCB5ID0gIkZpdC1TTkUgMiIpICsgCiAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCAKICAgICAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSAiY2VudGVyIiwgCiAgICAgICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpICsgCiAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMSwgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKQpwNCA8LSBEaW1QbG90KHJlY2x1c3RfcmVzW1szXV0sIGNvbHMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmRlZmF1bHRfbG9jdXN6b29tIikpICsgCiAgICAgIGxhYnMoeCA9ICJGaXQtU05FIDEiLCB5ID0gIkZpdC1TTkUgMiIpICsgCiAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCAKICAgICAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSAiY2VudGVyIiwgCiAgICAgICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpICsgCiAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMSwgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKQpwMgpwMwpwNApgYGAKCk5leHQsIHdlJ2xsIHJlaW50ZWdyYXRlIG91ciBuZXcgY2x1c3RlcnMgaW50byBvdXIgb3JpZ2luYWwgYFNldXJhdGAgb2JqZWN0IC0gdGhpcyByZXF1aXJlcyBzb21lIGZpbmFnbGluZyBhcyBgU2V1cmF0YCBpcyBhIGJpdCB3ZWlyZCB3aXRoIGhvdyBpdCBzdG9yZXMgY2VsbC1sZXZlbCBtZXRhZGF0YS4gU2luY2Ugd2UgaGFkIHNpeCBjbHVzdGVycyBvcmlnaW5hbGx5LCBhbmQgd2UgZGlzY292ZXJlZCBzaXggbmV3IHN1YmNsdXN0ZXJzLCB3ZSdsbCBlbmQgdXAgd2l0aCB0d2VsdmUgdG90YWwgY2x1c3RlcnMuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KY2x1c3RfZGYgPC0gZGF0YS5mcmFtZShDZWxsSUQgPSBjb2xuYW1lcyhwYm1jKSwgCiAgICAgICAgICAgICAgICAgICAgICAgQ2x1c3RJRCA9IGFzLm51bWVyaWMocGJtY0BtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzKSAtIDEsIAogICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKY2x1c3RfNiA8LSByb3duYW1lcyhyZWNsdXN0X3Jlc1tbMV1dQG1ldGEuZGF0YVtyZWNsdXN0X3Jlc1tbMV1dQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgPT0gMSwgXSkKY2x1c3RfNyA8LSByb3duYW1lcyhyZWNsdXN0X3Jlc1tbMV1dQG1ldGEuZGF0YVtyZWNsdXN0X3Jlc1tbMV1dQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgPT0gMiwgXSkKY2x1c3RfOCA8LSByb3duYW1lcyhyZWNsdXN0X3Jlc1tbMV1dQG1ldGEuZGF0YVtyZWNsdXN0X3Jlc1tbMV1dQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgPT0gMywgXSkKY2x1c3RfOSA8LSByb3duYW1lcyhyZWNsdXN0X3Jlc1tbMl1dQG1ldGEuZGF0YVtyZWNsdXN0X3Jlc1tbMl1dQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgPT0gMSwgXSkKY2x1c3RfMTAgPC0gcm93bmFtZXMocmVjbHVzdF9yZXNbWzNdXUBtZXRhLmRhdGFbcmVjbHVzdF9yZXNbWzNdXUBtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzID09IDEsIF0pCmNsdXN0XzExIDwtIHJvd25hbWVzKHJlY2x1c3RfcmVzW1szXV1AbWV0YS5kYXRhW3JlY2x1c3RfcmVzW1szXV1AbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSAyLCBdKQpsYWJlbCA8LSBjYXNlX3doZW4oY2x1c3RfZGYkQ2VsbElEICVpbiUgY2x1c3RfNiB+IDYsIAogICAgICAgICAgICAgICAgICAgY2x1c3RfZGYkQ2VsbElEICVpbiUgY2x1c3RfNyB+IDcsIAogICAgICAgICAgICAgICAgICAgY2x1c3RfZGYkQ2VsbElEICVpbiUgY2x1c3RfOCB+IDgsIAogICAgICAgICAgICAgICAgICAgY2x1c3RfZGYkQ2VsbElEICVpbiUgY2x1c3RfOSB+IDksIAogICAgICAgICAgICAgICAgICAgY2x1c3RfZGYkQ2VsbElEICVpbiUgY2x1c3RfMTAgfiAxMCwgCiAgICAgICAgICAgICAgICAgICBjbHVzdF9kZiRDZWxsSUQgJWluJSBjbHVzdF8xMSB+IDExLCAKICAgICAgICAgICAgICAgICAgIFRSVUUgfiBjbHVzdF9kZiRDbHVzdElEKQpwYm1jIDwtIEFkZE1ldGFEYXRhKHBibWMsIAogICAgICAgICAgICAgICAgICAgIGNvbC5uYW1lID0gInNldXJhdF9jbHVzdGVycyIsIAogICAgICAgICAgICAgICAgICAgIG1ldGFkYXRhID0gYXMuZmFjdG9yKGxhYmVsKSkKSWRlbnRzKHBibWMpIDwtICJzZXVyYXRfY2x1c3RlcnMiCnA1IDwtIERpbVBsb3QocGJtYywgY29scyA9IGNvbHMyNSgpKSArIAogICAgICBsYWJzKHggPSAiRml0LVNORSAxIiwgeSA9ICJGaXQtU05FIDIiKSArIAogICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgCiAgICAgICAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImNlbnRlciIsIAogICAgICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKSArIAogICAgICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQobnJvdyA9IDEsIG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDMpKSkKcDUKYGBgCgojIElkZW50aWZ5IENlbGwgVHlwZXMKCk5vdyB0aGF0IHdlJ3ZlIGRldGVybWluZWQgb3VyIHN1YnBvcHVsYXRpb25zLCB3ZSBjYW4gYXNzaWduIGNlbGwgdHlwZXMgdG8gZWFjaCBjbHVzdGVyIHVzaW5nIHRoZSBtYXJrZXIgZ2VuZXMgcHJvdmlkZWQgaW4gW3RoZSBTYXRpamEgTGFiJ3MgUEJNQzNrIHZpZ25ldHRlXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L3YzLjIvcGJtYzNrX3R1dG9yaWFsLmh0bWwpLCBhcyB3ZWxsIGFzIG90aGVyIGNhbm9uaWNhbCBtYXJrZXJzLgoKIyMgQ0Q0KyBUIENlbGxzCgpXZSBjYW4gcXVpY2tseSBpZGVudGlmeSBjbHVzdGVyIDAgYXMgdGhlIG1lbW9yeSBDRDQrIFQgY2VsbHMsIGFuZCBjbHVzdGVyIDYgYXMgdGhlIG5haXZlIENENCsgcG9wdWxhdGlvbi4KCmBgYHtyfQpwNiA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSAiSUw3UiIsIAogICAgICAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImZpcmVicmljazMiKSkgKyAKICAgICAgTm9MZWdlbmQoKSArIAogICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpwNyA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSAiQ0NSNyIsIAogICAgICAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImZpcmVicmljazMiKSkgKyAKICAgICAgTm9MZWdlbmQoKSArIAogICAgICB0aGVtZV95ZWhsYWIoKSAgKyAKICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDggPC0gRmVhdHVyZVBsb3QocGJtYywgCiAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIlMxMDBBNCIsIAogICAgICAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImZpcmVicmljazMiKSkgKyAKICAgICAgTm9MZWdlbmQoKSArIAogICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpwOSA8LSAocDYgfCBwNyB8IHA4KSAvIHA1CnA5CmBgYAoKQ2x1c3RlciA3IGlzIG9ubHkgc3VidGx5IHNlcGFyYXRlZCBmcm9tIHRoZSBDRDQrIFQgY2VsbCBjbHVzdGVycy4gV2UnbGwgdXNlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIHRlc3RpbmcgdG8gZGV0ZXJtaW5lIGlmIHRoZSBjZWxscyBpbiBjbHVzdGVyIDcgYXJlIGEgc3B1cmlvdXMgY2x1c3RlciBvciBhIHJlYWwgVCBjZWxsIHN1YnR5cGUuIEFmdGVyIHJ1bm5pbmcgYSBXaWxjb3ggdGVzdCB3ZSBjYW4gc2VlIHRoYXQgc2V2ZXJhbCBvZiB0aGUgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGFyZSBhc3NvY2lhdGVkIHdpdGggdGhlIGludGVyZmVyb24gZmFtaWx5IG9mIGN5dG9raW5lcyBhbmQgd2l0aCBhbnRpLXZpcmFsIGltbXVuZSByZXNwb25zZXMuCgpgYGB7cn0KY2x1c3Q3X21hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMocGJtYywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlNDVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dmYy50aHJlc2hvbGQgPSAuNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9ubHkucG9zID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QudXNlID0gIndpbGNveCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYW5kb20uc2VlZCA9IDYyOSkgJT4lIAogICAgICAgICAgICAgICAgICBzdWJzZXQoY2x1c3RlciAgPT0gNyAmIHBfdmFsX2FkaiA8IC4wNSkgCmNsdXN0N19tYXJrZXJzCmBgYAoKVXBvbiB2aXN1YWwgaW5zcGVjdGlvbiBvZiB0aGUgdG9wIHRocmVlIG1hcmtlciBnZW5lcyAoYXMgZGV0ZXJtaW5lZCBieSBhZGp1c3RlZCBwLXZhbHVlKSwgd2UgY2FuIHNlZSB0aGF0IHRoZXkgZG8gYW4gZXF1YWxseSBnb29kIGpvYiBvZiBkaXN0aW5ndWlzaGluZyB0aGUgc21hbGwgY2x1c3RlciBmcm9tIHRoZSBzYW1wbGUgYXMgYSB3aG9sZSBhcyB0aGV5IGRvIGF0IHNlcGFyYXRpbmcgaXQgZnJvbSB0aGUgbWVtb3J5IENENCsgVCBjZWxscy4gRHVlIHRvIHRoZWlyIGFudGktdmlyYWwgY2hhcmFjdGVyaXN0aWNzICwgd2UnbGwgZGVmaW5lIHRoaXMgZ3JvdXAgYXMgYmVpbmcgY29tcG9zZWQgb2YgVHlwZSAxIGhlbHBlciBUIGNlbGxzIChUaDEpLgoKYGBge3J9CnAxMCA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIklGSVQxIiwgCiAgICAgICAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImZpcmVicmljazMiKSkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpwMTEgPC0gRmVhdHVyZVBsb3QocGJtYywgCiAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIklGSVQzIiwgCiAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAxMiA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSAiSUZJNiIsIAogICAgICAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImZpcmVicmljazMiKSkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpwMTMgPC0gKHAxMCB8IHAxMSB8IHAxMikgLyBwNQpwMTMKYGBgCgojIyBDRDE0KyBNb25vY3l0ZXMKCkNsdXN0ZXIgMSBjbGVhcmx5IGhvdXNlcyBvdXIgQ0QxNCsgbW9ub2N5dGVzLgoKYGBge3J9CnAxNCA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIkNEMTQiLCAKICAgICAgICAgICAgICAgICAgY29scyA9IGMoImxpZ2h0Z3JleSIsICJmaXJlYnJpY2szIikpICsgCiAgICAgICBOb0xlZ2VuZCgpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDE1IDwtIEZlYXR1cmVQbG90KHBibWMsIAogICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSAiTFlaIiwgCiAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAxNiA8LSAocDE0IHwgcDE1KSAvIHA1CnAxNgpgYGAKCiMjIEZDR1IzQSsgTW9ub2N5dGVzCgpJdCBmb2xsb3dzIHRoYXQgdGhlIEZDR1IzQSsgbW9ub2N5dGVzIGFyZSBwb3NpdGlvbmVkIG5leHQgdG8gdGhlIENEMTQrIG1vbm9jeXRlcyBpbiBjbHVzdGVyIDQuCgpgYGB7cn0KcDE3IDwtIEZlYXR1cmVQbG90KHBibWMsIAogICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSAiRkNHUjNBIiwgCiAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAxOCA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIk1TNEE3IiwgCiAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAxOSA8LSAocDE3IHwgcDE4KSAvIHA1CnAxOQpgYGAKCiMjIEIgQ2VsbHMKCkV4cHJlc3Npb24gb2YgTVM0QTEgYWxsb3dzIHVzIHRvIGlzb2xhdGUgdGhlIEIgY2VsbHMgYXMgYmVsb25naW5nIHRvIGNsdXN0ZXIgMy4KCmBgYHtyfQpwMjAgPC0gRmVhdHVyZVBsb3QocGJtYywgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9ICJNUzRBMSIsIAogICAgICAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImZpcmVicmljazMiKSkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpwMjEgPC0gcDIwIC8gcDUKcDIxCmBgYAoKIyMgQ0Q4KyBUIENlbGxzCgpUaGUgY2Fub25pY2FsIG1hcmtlciBDRDhBIHN3aWZ0bHkgaWRlbnRpZmllcyBvdXIgQ0Q4KyBUIGNlbGxzIGluIGNsdXN0ZXIgMi4KCmBgYHtyfQpwMjIgPC0gRmVhdHVyZVBsb3QocGJtYywgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9ICJDRDhBIiwgCiAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAyMyA8LSBwMjIgLyBwNQpwMjMKYGBgCgojIyBOYXR1cmFsIEtpbGxlciBDZWxscwoKV2UgY2FuIHVzZSBOS0c3IGFuZCBHTkxZIHRvIGlzb2xhdGUgdGhlIE5LIGNlbGxzIGluIGNsdXN0ZXIgNS4KCmBgYHtyfQpwMjQgPC0gRmVhdHVyZVBsb3QocGJtYywgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9ICJOS0c3IiwgCiAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAyNSA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIkdOTFkiLCAKICAgICAgICAgICAgICAgICAgY29scyA9IGMoImxpZ2h0Z3JleSIsICJmaXJlYnJpY2szIikpICsgCiAgICAgICBOb0xlZ2VuZCgpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDI2IDwtIChwMjQgfCBwMjUpIC8gcDUKcDI2CmBgYAoKIyMgRGVuZHJpdGljIENlbGxzCgpUaGUgZGVuZHJpdGljIGNlbGwgZ3JvdXAgaXMgZGVmaW5lZCBieSBleHByZXNzaW9uIG9mIEZDRVIxQSBhbmQgQ1NUMyBpbiBjbHVzdGVyIDkuCgpgYGB7cn0KcDI3IDwtIEZlYXR1cmVQbG90KHBibWMsIAogICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSAiRkNFUjFBIiwgCiAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAyOCA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIkNTVDMiLCAKICAgICAgICAgICAgICAgICAgY29scyA9IGMoImxpZ2h0Z3JleSIsICJmaXJlYnJpY2szIikpICsgCiAgICAgICBOb0xlZ2VuZCgpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDI5IDwtIChwMjcgfCBwMjgpIC8gcDUKcDI5CmBgYAoKIyMgUGxhdGVsZXRzCgpUaGUgdGlueSBwbGF0ZWxldCBwb3B1bGF0aW9uIG9mIGByIHN1bShwYm1jJHNldXJhdF9jbHVzdGVycyA9PSAxMClgIGNlbGxzIGNhbiBiZSBpZGVudGlmaWVkIGJ5IGl0cyBleHByZXNzaW9uIG9mIFBQQlAgaW4gY2x1c3RlciAxMC4KCmBgYHtyfQpwMzAgPC0gRmVhdHVyZVBsb3QocGJtYywgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9ICJQUEJQIiwgCiAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAzMSA8LSBwMzAgLyBwNQpwMzEKYGBgCgojIyBQbGFzbWFjeXRvaWQgRGVuZHJpdGljIENlbGxzCgpBIHZlcnkgc21hbGwgcG9wdWxhdGlvbiBvZiBwbGFzbWFjeXRvaWQgZGVuZHJpdGljIGNlbGxzIC0gb25seSBgciBzdW0ocGJtYyRzZXVyYXRfY2x1c3RlcnMgPT0gMTEpYCBjZWxscyAtIHdhcyB0ZWFzZWQgb3V0IGJ5IGBTQ0lTU09SU2AgYW5kIGNhbiBiZSBhbm5vdGF0ZWQgdXNpbmcgZXhwcmVzc2lvbiBvZiBNWkIxLgoKYGBge3J9CnAzMiA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIk1aQjEiLCAKICAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZmlyZWJyaWNrMyIpKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAzMyA8LSBwMzIgLyBwNQpwMzMKYGBgCgojIyBIZW1hdG9jeXRvYmxhc3RzCgpMYXN0bHksIHdlIG5lZWQgdG8gYXNzaWduIGFuIGlkZW50aXR5IHRvIHRoZSB1bmtub3duIGNsdXN0ZXIgOCAtIGFub3RoZXIgcmFyZSBjZWxsIHBvcHVsYXRpb24sIGFsc28gY29tcG9zZWQgb2Ygb25seSBgciBzdW0ocGJtYyRzZXVyYXRfY2x1c3RlcnMgPT0gOClgIGNlbGxzLiBXZSdsbCB1c2UgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gdG8gY29tcGFyZSBpdCBhcyB3ZSBkaWQgZWFybGllciB3aXRoIHRoZSBUaDEgY2VsbHMuIFRoZSBtYXJrZXIgQ1lUTDEgaXMgb3Zlci1leHByZXNzZWQgaW4gaGVtYXRvcG9ldGljIHN0ZW0gY2VsbHMgKGhlbWF0b2N5dG9ibGFzdHMpLgoKYGBge3J9CmNsdXN0OF9tYXJrZXJzIDwtIEZpbmRBbGxNYXJrZXJzKHBibWMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0LnVzZSA9ICJ3aWxjb3giLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb25seS5wb3MgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nZmMudGhyZXNob2xkID0gLjc1LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiU0NUIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbS5zZWVkID0gNjI5KSAlPiUgCiAgICAgICAgICAgICAgICAgIHN1YnNldChjbHVzdGVyID09IDggJiBwX3ZhbF9hZGogPCAuMDUpCmNsdXN0OF9tYXJrZXJzCmBgYAoKYGBge3J9CnAzNCA8LSBGZWF0dXJlUGxvdChwYm1jLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gIkNZVEwxIiwgCiAgICAgICAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImZpcmVicmljazMiKSkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpwMzUgPC0gcDM0IC8gcDUKcDM1CmBgYAoKIyMgRmluYWwgRmlndXJlCgpGaW5hbGx5LCB3ZSdsbCBhZGQgY2VsbCBsYWJlbHMgdG8gb3VyIG9yaWdpbmFsIGBTZXVyYXRgIG9iamVjdCBhbmQgcGxvdCB0aGUgcmVzdWx0cy4KCmBgYHtyfQpwYm1jQG1ldGEuZGF0YSRsYWJlbCA8LSBjYXNlX3doZW4ocGJtY0BtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzID09IDAgfiAiTWVtb3J5IENENCsgVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGJtY0BtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzID09IDEgfiAiQ0QxNCsgTW9ub2N5dGUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBibWNAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSAyIH4gIkNEOCsgVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGJtY0BtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzID09IDMgfiAiQiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGJtY0BtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzID09IDQgfiAiRkNHUjNBKyBNb25vY3l0ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGJtY0BtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzID09IDUgfiAiTksiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBibWNAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSA2IH4gIk5haXZlIENENCsgVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGJtY0BtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzID09IDcgfiAiVGgxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYm1jQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgPT0gOCB+ICJIZW1hdG9jeXRvYmxhc3QiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBibWNAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSA5IH4gIkRDIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYm1jQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgPT0gMTAgfiAiUGxhdGVsZXQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBibWNAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSAxMSB+ICJQbGFzbWFjeXRvaWQgREMiKQpgYGAKCldlIHJlZGVmaW5lIHRoZSBmaW5hbCBjb2xvciBwYWxldHRlIHRvIGJlIHVzZWQgaW4gb3JkZXIgdG8gaGF2ZSBhIGJldHRlci1sb29raW5nIGZpZ3VyZS4gTm90ZTogaWYgeW91J3JlIG1hbnVhbGx5IGRlZmluaW5nIGNvbG9ycyBhbmQgd2FudCBgU2V1cmF0YCB0byBhc3NpZ24gYSBjZXJ0YWluIGNvbG9yIHRvIGEgY2VydGFpbiBjZWxsIHR5cGUgb3IgY2x1c3RlciBudW1iZXIsIHlvdSBuZWVkIHRvIG5hbWUgdGhlIHZlY3RvciBvZiBjb2xvcnMgeW91IHByb3ZpZGUuIAoKYGBge3J9CmZpbmFsX3BhbCA8LSBjKCJwYWxldHVycXVvaXNlNCIsICJnb2xkZW5yb2QiLCAic3RlZWxibHVlMSIsICJsaWdodHNlYWdyZWVuIiwgCiAgICAgICAgICAgICAgICJzaWVubmE0IiwgIm1lZGl1bWJsdWUiLCAiY29yYWwzIiwgIm1hZ2VudGEiLCAibGltZWdyZWVuIiwgCiAgICAgICAgICAgICAgICJmb3Jlc3RncmVlbiIsICJkYXJrb3JjaGlkMyIsICJvcmFuZ2UiKQpuYW1lcyhmaW5hbF9wYWwpIDwtIGMoIk1lbW9yeSBDRDQrIFQiLCAiQ0QxNCsgTW9ub2N5dGUiLCAiQ0Q4KyBUIiwgIkIiLCAKICAgICAgICAgICAgICAgICAgICAgICJGQ0dSM0ErIE1vbm9jeXRlIiwgIk5LIiwgIk5haXZlIENENCsgVCIsICJUaDEiLCAKICAgICAgICAgICAgICAgICAgICAgICJIZW1hdG9jeXRvYmxhc3QiLCAiREMiLCAiUGxhdGVsZXQiLCAiUGxhc21hY3l0b2lkIERDIikKcDM2IDwtIERpbVBsb3QocGJtYywgY29scyA9IGZpbmFsX3BhbCwgZ3JvdXAuYnkgPSAibGFiZWwiKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCAKICAgICAgICAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImNlbnRlciIsIAogICAgICAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExKSkgKyAKICAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMywgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKSArIAogICAgICAgbGFicyh4ID0gIkZpdC1TTkUgMSIsIHkgPSAiRml0LVNORSAyIiwgdGl0bGUgPSBOVUxMKQpwMzYKYGBgCgojIENvbmNsdXNpb25zCgpTQ0lTU09SUyByZXZlYWxlZCB0aW55IHBsYXRlbGV0IGFuZCBwbGFzbWFjeXRvaWQgZGVuZHJpdGljIGNlbGwgY2x1c3RlcnMgdGhhdCB3ZXJlIGluaXRpYWxseSBncm91cGVkIHdpdGggdGhlIENEOCsgVCBjZWxscywgYW5kIGl0IGhlbHBlZCB1cyB0byBzZXBhcmF0ZSB0aGUgZGVuZHJpdGljIGNlbGxzIGZyb20gdGhlIGxhcmdlciBDRDE0KyBtb25vY3l0ZSBjbHVzdGVyLiBJdCBhbHNvIHNwbGl0IHVwIHRoZSBuYWl2ZSBhbmQgbWVtb3J5IENENCsgVCBjZWxscywgYW5kIHNob3dlZCB1cyBhIHRpbnkgVGgxIGNlbGwgc3Vic2V0IHRoYXQgd2FzIG5vdCBpbml0aWFsbHkgdmlzaWJsZS4gVGhlIHBsYXNtYWN5dG9pZCBEQ3MgYW5kIFRoMSBjZWxscyB3ZXJlIG5vdCBhbm5vdGF0ZWQgaW4gW3RoZSBvcmlnaW5hbCBTYXRpamEgTGFiIFBCTUMzayB2aWduZXR0ZV0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC92My4yL3BibWMza190dXRvcmlhbC5odG1sKS4gCgpXZSB1c2VkIHRoZSBQQk1DM2sgZGF0YXNldCBiZWNhdXNlIG9mIDEpIGl0cyBpbW1lZGlhdGUgYXZhaWxhYmlsaXR5IHRvIGFueW9uZSB3aXNoaW5nIHRvIHJlcGxpY2F0ZSBvdXIgcmVzdWx0cyBhbmQgMikgdGhlIHZhbGlkaXR5IG9mIGl0cyBhbm5vdGF0aW9ucywgd2hpY2ggYWxsb3dlZCB1cyB0byBiZSBjb25maWRlbnQgaW4gdGhlIHJlc3VsdHMgZnJvbSBTQ0lTU09SUywgd2hpY2ggd2FzIGFibGUgdG8gY2FydmUgb3V0IHJhcmUgY2VsbCBncm91cHMgZnJvbSBsYXJnZXIsIGJyb2FkZXIgY2VsbCB0eXBlcy4gSW4gdGhpcyBjYXNlLCB0aGUgZGVuZHJpdGljIGNlbGwgY2x1c3RlciB3YXMgY29tcG9zZWQgb2YgMzEgY2VsbHMsIHRoZSBwbGF0ZWxldCBjbHVzdGVyIG9mIDExIGNlbGxzLCB0aGUgVGgxIGNsdXN0ZXIgb2YgMjUgY2VsbHMsIGFuZCB0aGUgbWludXNjdWxlIHBsYXNtYWN5dG9pZCBEQyBjbHVzdGVyIG9mIGp1c3QgNCBjZWxscy4gUmVzcGVjdGl2ZWx5LCB0aGVzZSBjZWxsIHR5cGVzIG1hZGUgdXAgMS4xNSUsIDAuNDElLCAwLjkzJSwgYW5kIDAuMTUlIG9mIHRoZSBlbnRpcmUgc2FtcGxlLiBXZSB0aHVzIGJlbGlldmUgd2UgY2FuIGNvbmZpZGVudGx5IHNheSB0aGF0IFNDSVNTT1JTIGhhcyBiZWVuIHNob3duIHRvIGFjY3VyYXRlbHkgYW5kIHN3aWZ0bHkgaWRlbnRpZnkgcmFyZSBjZWxsIHR5cGVzIGJ5IGNvbnNpZGVyaW5nIHRoZSB2YXJpYW5jZSBpbiBnZW5lIGV4cHJlc3Npb24gd2l0aGluIGNsdXN0ZXJzIGFuZCBqdWRnaW5nIGl0ZXJhdGl2ZSByZWNsdXN0ZXJpbmcgdXNpbmcgc2lsaG91ZXR0ZSBzY29yZXMsIHJhdGhlciB0aGFuIGF0dGVtcHRpbmcgdG8gaWRlbnRpZnkgcmFyZSBjZWxsIHBvcHVsYXRpb25zIGF0IHRoZSBsZXZlbCBvZiB0aGUgZW50aXJlIGRhdGFzZXQuCgojIFNhdmUgRGF0YSAmIEZpZ3VyZXMKClRoaXMgcGFydCBpc24ndCByZWFsbHkgd29ydGggcmVhZGluZzsgaXQncyBqdXN0IGhlcmUgdG8gcHJvdmUgdGhhdCBhbGwgdGhlIGZpZ3VyZXMgd2VyZSBhY3R1YWxseSBkeW5hbWljYWxseSBnZW5lcmF0ZWQgYW5kIHNhdmVkIHVwb24ga25pdHRpbmcgdGhpcyBkb2N1bWVudC4KCmBgYHtyLCBldmFsPUZBTFNFfQpwZHJpdmVfZGlyIDwtICIvVm9sdW1lcy9sYWJzL0hvbWUvSmVuIEplbiBZZWggTGFiL0phY2svUmVjbHVzdGVyaW5nIFByb2plY3QvU2V1cmF0IE9iamVjdHMvcGJtYzNrXzIwMjEwMS5SZHMiCnNhdmVSRFMocGJtYywgZmlsZSA9IHBkcml2ZV9kaXIpCmBgYAoKV2UnbGwgY3JlYXRlIGEgcXVpY2sgY29udmVuaWVuY2UgZnVuY3Rpb24gdG8gaGVscCB1cyBzYXZlIHRoZSBmaWd1cmVzLgoKYGBge3J9CnNhdmVTQ0lTU09SUyA8LSBmdW5jdGlvbihwbG90ID0gTlVMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gTlVMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXIgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHB1Yi5yZWFkeSA9IEZBTFNFKSB7CiAgaWYgKGlzLm51bGwocGxvdCkgfCBpcy5udWxsKG5hbWUpKSBzdG9wKCJZb3UgZm9yZ290IHNvbWUgYXJndW1lbnRzLiIpCiAgaWYgKHB1Yi5yZWFkeSkgewogICAgZGlyIDwtICJ+L0Rlc2t0b3AvUi9TQ0lTU09SUy92aWduZXR0ZXMvZmlndXJlc19wdWIvUEJNQyIKICAgIGlmICghYm9yZGVyKSB7CiAgICAgIHBsb3QgPC0gcGxvdCArIAogICAgICAgICAgICAgIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQogICAgfSBlbHNlIHsKICAgICAgcGxvdCA8LSBwbG90ICsgCiAgICAgICAgICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQogICAgfQogICAgZ2dzYXZlKGZpbGVuYW1lID0gcGFzdGUwKG5hbWUsICIucGRmIiksIAogICAgICAgICAgIGRldmljZSA9ICJwZGYiLCAKICAgICAgICAgICB1bml0cyA9ICJpbiIsCiAgICAgICAgICAgcGF0aCA9IGRpciwgCiAgICAgICAgICAgaGVpZ2h0ID0gOCwgCiAgICAgICAgICAgd2lkdGggPSA4KSAKICB9IGVsc2UgewogICAgZGlyIDwtICJ+L0Rlc2t0b3AvUi9TQ0lTU09SUy92aWduZXR0ZXMvZmlndXJlc19zdXBwL1BCTUMiCiAgICBnZ3NhdmUoZmlsZW5hbWUgPSBwYXN0ZTAobmFtZSwgIi5wZGYiKSwgCiAgICAgICAgICAgZGV2aWNlID0gInBkZiIsIAogICAgICAgICAgIHVuaXRzID0gImluIiwKICAgICAgICAgICBwYXRoID0gZGlyLCAKICAgICAgICAgICBoZWlnaHQgPSA4LCAKICAgICAgICAgICB3aWR0aCA9IDgpIAogIH0KfQpgYGAKCkxhc3RseSwgd2UnbGwgc2F2ZSB0aGUgZmlndXJlcyB1bmRlciBgLi92aWduZXR0ZXMvZmlndXJlcy9gLiAKCmBgYHtyfQpzYXZlU0NJU1NPUlMocGxvdCA9IHAwLCBuYW1lID0gIlNldXJhdF9DbHVzdGVycyIsIAogICAgICAgICAgICAgcHViLnJlYWR5ID0gVFJVRSwgYm9yZGVyID0gRkFMU0UpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDEsIG5hbWUgPSAiU2V1cmF0X0NsdXN0ZXJzX0ZpdFNORSIsIAogICAgICAgICAgICAgcHViLnJlYWR5ID0gVFJVRSwgYm9yZGVyID0gRkFMU0UpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDIsIG5hbWUgPSAiQ2x1c3QwX1JlY2x1c3QiLCAKICAgICAgICAgICAgIHB1Yi5yZWFkeSA9IFRSVUUsIGJvcmRlciA9IEZBTFNFKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAzLCBuYW1lID0gIkNsdXN0MV9SZWNsdXN0IiwgCiAgICAgICAgICAgICBwdWIucmVhZHkgPSBUUlVFLCBib3JkZXIgPSBGQUxTRSkKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwNCwgbmFtZSA9ICJDbHVzdDJfUmVjbHVzdCIsIAogICAgICAgICAgICAgcHViLnJlYWR5ID0gVFJVRSwgYm9yZGVyID0gRkFMU0UpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDUsIG5hbWUgPSAiU0NJU1NPUlNfQ2x1c3RlcnMiLCAKICAgICAgICAgICAgIHB1Yi5yZWFkeSA9IFRSVUUsIGJvcmRlciA9IEZBTFNFKQpzYXZlU0NJU1NPUlMocGxvdCA9IHA2LCBuYW1lID0gIkNENFRfSUw3UiIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDcsIG5hbWUgPSAiQ0Q0VF9DQ1I3IikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwOCwgbmFtZSA9ICJDRDRUX1MxMDBBNCIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDEwLCBuYW1lID0gIlRIMV9JRklUMSIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDExLCBuYW1lID0gIlRIMV9JRklUMyIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDEyLCBuYW1lID0gIlRIMV9JRkk2IikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMTQsIG5hbWUgPSAiTW9ub2N5dGVfQ0QxNCIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDE1LCBuYW1lID0gIk1vbm9jeXRlX0xZWiIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDE3LCBuYW1lID0gIkZDR1IzQV9Nb25vY3l0ZV9GQ0dSM0EiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAxOCwgbmFtZSA9ICJGQ0dSM0FfTW9ub2N5dGVfTVM0QTciKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAyMCwgbmFtZSA9ICJCX01TNEExIikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMjIsIG5hbWUgPSAiQ0Q4VF9DRDhBIikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMjQsIG5hbWUgPSAiTktfTktHNyIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDI1LCBuYW1lID0gIk5LX0dOTFkiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAyNywgbmFtZSA9ICJEQ19GQ0VSMUEiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAyOCwgbmFtZSA9ICJEQ19DU1QzIikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMzAsIG5hbWUgPSAiUGxhdGVsZXRfUFBCUCIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDMyLCBuYW1lID0gIlBsYXNtYWN5dG9pZF9EQ19NWkIxIikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMzQsIG5hbWUgPSAiSGVtYXRvY3l0b2JsYXN0c19DWVRMMSIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDM2LCBuYW1lID0gIlNDSVNTT1JTX0ZpbmFsX0Fubm90YXRpb25zIiwgCiAgICAgICAgICAgICBwdWIucmVhZHkgPSBUUlVFLCBib3JkZXIgPSBGQUxTRSkKYGBgCgpBbmQgb2YgY291cnNlOgoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCg==